Driver config reporting — stage 2: full DRIVER_CONFIG report - #968
Driver config reporting — stage 2: full DRIVER_CONFIG report#968nikagra wants to merge 35 commits into
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThe driver now reports expanded default-profile configuration through control-connection Sequence Diagram(s)sequenceDiagram
participant StartupOptionsBuilder
participant ProtocolInitHandler
participant FeatureStore
participant DriverConfigReporter
StartupOptionsBuilder->>ProtocolInitHandler: provide stable SESSION_ID
ProtocolInitHandler->>FeatureStore: read sharding information
ProtocolInitHandler->>DriverConfigReporter: build control-connection DRIVER_CONFIG
Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
Comment |
ffe0609 to
9a2f6ca
Compare
9a2f6ca to
c6f7ca3
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java`:
- Around line 1178-1187: Make the reporting documentation backend-neutral across
DefaultDriverOption, TypedDriverOption, and reference.conf: replace
ScyllaDB-only wording with server-side terminology or explicitly document both
storage paths, system.clients for ScyllaDB and system_views.clients for
Cassandra 4.1. Update all three affected sites consistently without changing the
reporting behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: ab5c16aa-71fa-4f9a-9659-654b6920ae50
📒 Files selected for processing (12)
core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.javacore/src/main/java/com/datastax/oss/driver/api/core/config/TypedDriverOption.javacore/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.javacore/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverConfigReporter.javacore/src/main/java/com/datastax/oss/driver/internal/core/context/DriverConfigReporter.javacore/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/CassandraSchemaQueries.javacore/src/main/resources/reference.confcore/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.javacore/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.javacore/src/test/java/com/datastax/oss/driver/internal/core/context/DefaultDriverConfigReporterTest.javaintegration-tests/src/test/java/com/datastax/oss/driver/core/config/DriverConfigReportingCcmIT.javaintegration-tests/src/test/java/com/datastax/oss/driver/core/config/DriverConfigReportingSimulacronIT.java
c6f7ca3 to
24062e9
Compare
There was a problem hiding this comment.
🧹 Nitpick comments (4)
core/src/main/java/com/datastax/oss/driver/internal/core/context/DriverConfigReporter.java (1)
51-57: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueContract extension is consistent with implementation and callers.
The new
scyllaDbparam and its "only meaningful withreportDriverConfig" contract matchDefaultDriverConfigReporter.populateStartupOptionsandProtocolInitHandler's caller.One minor note for the future: this interface now has two adjacent
booleanparameters (reportDriverConfig,scyllaDb), which is a classic call-site readability/mix-up risk (e.g.populateStartupOptions(opts, true, false)reads ambiguously without named-parameter comments, as seen in the test file). Not blocking, but if a third flag is ever added, consider a small options value object instead.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@core/src/main/java/com/datastax/oss/driver/internal/core/context/DriverConfigReporter.java` around lines 51 - 57, The comment identifies no required code change; the current scyllaDb parameter and contract are consistent with the implementation and callers. Leave DriverConfigReporter.populateStartupOptions and its call sites unchanged, and only consider introducing an options value object if another boolean flag is added later.core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java (1)
194-210: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winCentralize the ScyllaDB predicate. Both this startup path and
CassandraSchemaQueries.shouldApplyUsingTimeout()key off the sameshardingInfo != nullsignal; a shared helper would keep control-plane reporting and schema-query behavior in sync.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java` around lines 194 - 210, Centralize the ScyllaDB detection based on getShardingInfo() != null in a shared helper, then update ProtocolInitHandler’s startup reporting and CassandraSchemaQueries.shouldApplyUsingTimeout() to use it. Preserve the existing featureStore population flow and behavior while ensuring both paths rely on the same predicate.core/src/test/java/com/datastax/oss/driver/internal/core/context/DefaultDriverConfigReporterTest.java (1)
136-145: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
controlno longer exercises the control-connection path.Both calls pass
reportDriverConfig=false, so the map namedcontrolis identical topool. Passingtruefor the control map keeps the test name honest and additionally proves the session id is stable when the config blob is built.♻️ Suggested tweak
- reporter.populateStartupOptions(control, false, false); + reporter.populateStartupOptions(control, /* reportDriverConfig= */ true, false); reporter.populateStartupOptions(pool, false, false);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@core/src/test/java/com/datastax/oss/driver/internal/core/context/DefaultDriverConfigReporterTest.java` around lines 136 - 145, Update should_use_a_stable_session_id_across_connections so the control map calls reporter.populateStartupOptions with reportDriverConfig=true, while keeping the pool call false and preserving the session ID equality assertion.integration-tests/src/test/java/com/datastax/oss/driver/core/config/DriverConfigReportingCcmIT.java (1)
145-169: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winStage-2 payload assertion is copy-pasted across both integration tests. Both classes carry an identical
assertDriverConfigPayload(same Javadoc, same checks); every future stage-2 assertion has to be added twice and will silently drift otherwise.
integration-tests/src/test/java/com/datastax/oss/driver/core/config/DriverConfigReportingCcmIT.java#L145-L169: move this helper into a shared test utility (e.g. a package-privateDriverConfigReportAssertionsclass in this package) and call it from here.integration-tests/src/test/java/com/datastax/oss/driver/core/config/DriverConfigReportingSimulacronIT.java#L125-L149: delete the local copy and call the shared helper instead.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@integration-tests/src/test/java/com/datastax/oss/driver/core/config/DriverConfigReportingCcmIT.java` around lines 145 - 169, Extract the duplicated assertDriverConfigPayload helper into a package-private shared DriverConfigReportAssertions test utility, preserving its existing JSON parsing and stage-2 validation checks. In integration-tests/src/test/java/com/datastax/oss/driver/core/config/DriverConfigReportingCcmIT.java lines 145-169, replace the local helper with a call to the shared utility; in integration-tests/src/test/java/com/datastax/oss/driver/core/config/DriverConfigReportingSimulacronIT.java lines 125-149, delete the local copy and call the same utility.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In
`@core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java`:
- Around line 194-210: Centralize the ScyllaDB detection based on
getShardingInfo() != null in a shared helper, then update ProtocolInitHandler’s
startup reporting and CassandraSchemaQueries.shouldApplyUsingTimeout() to use
it. Preserve the existing featureStore population flow and behavior while
ensuring both paths rely on the same predicate.
In
`@core/src/main/java/com/datastax/oss/driver/internal/core/context/DriverConfigReporter.java`:
- Around line 51-57: The comment identifies no required code change; the current
scyllaDb parameter and contract are consistent with the implementation and
callers. Leave DriverConfigReporter.populateStartupOptions and its call sites
unchanged, and only consider introducing an options value object if another
boolean flag is added later.
In
`@core/src/test/java/com/datastax/oss/driver/internal/core/context/DefaultDriverConfigReporterTest.java`:
- Around line 136-145: Update should_use_a_stable_session_id_across_connections
so the control map calls reporter.populateStartupOptions with
reportDriverConfig=true, while keeping the pool call false and preserving the
session ID equality assertion.
In
`@integration-tests/src/test/java/com/datastax/oss/driver/core/config/DriverConfigReportingCcmIT.java`:
- Around line 145-169: Extract the duplicated assertDriverConfigPayload helper
into a package-private shared DriverConfigReportAssertions test utility,
preserving its existing JSON parsing and stage-2 validation checks. In
integration-tests/src/test/java/com/datastax/oss/driver/core/config/DriverConfigReportingCcmIT.java
lines 145-169, replace the local helper with a call to the shared utility; in
integration-tests/src/test/java/com/datastax/oss/driver/core/config/DriverConfigReportingSimulacronIT.java
lines 125-149, delete the local copy and call the same utility.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 5db2451e-3027-4740-bf80-230694676062
📒 Files selected for processing (15)
core/pom.xmlcore/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.javacore/src/main/java/com/datastax/oss/driver/api/core/config/TypedDriverOption.javacore/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.javacore/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverConfigReporter.javacore/src/main/java/com/datastax/oss/driver/internal/core/context/DriverConfigReporter.javacore/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/CassandraSchemaQueries.javacore/src/main/resources/reference.confcore/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.javacore/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.javacore/src/test/java/com/datastax/oss/driver/internal/core/context/DefaultDriverConfigReporterTest.javacore/src/test/resources/config/driver-config-report-v1.schema.jsonintegration-tests/src/test/java/com/datastax/oss/driver/core/config/DriverConfigReportingCcmIT.javaintegration-tests/src/test/java/com/datastax/oss/driver/core/config/DriverConfigReportingSimulacronIT.javapom.xml
🚧 Files skipped from review as they are similar to previous changes (5)
- core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java
- core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/CassandraSchemaQueries.java
- core/src/main/resources/reference.conf
- core/src/main/java/com/datastax/oss/driver/api/core/config/TypedDriverOption.java
- core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java
24062e9 to
d6ac737
Compare
|
@coderabbitai Thanks for the review — dispositions for the nitpicks, all pushed into the amended stage-2 commit:
Unrelated to the nitpicks but included in the same push: a TLS reporting correctness fix — |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== Relevant files =="
fd -t f . core/src/main integration-tests/src/test | rg 'DefaultDriverConfigReporter|DriverConfigReporting|SslEngineFactory|ProtocolInitHandler'
echo
echo "== Updated stable-session test and config reporting calls =="
rg -n -C 5 'should_use_a_stable_session_id_across_connections|populateStartupOptions\s*\(' \
core/src/test/java/com/datastax/oss/driver/internal/core/context/DefaultDriverConfigReporterTest.java \
core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverConfigReporter.java \
core/src/main/java/com/datastax/oss/driver/internal/core/context/DriverConfigReporter.java \
core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java
echo
echo "== Shared integration assertion helper and callers =="
rg -n -C 4 'class DriverConfigReportingAssertions|assertDriverConfigPayload\s*\(' \
integration-tests/src/test/java/com/datastax/oss/driver/core/config
echo
echo "== TLS hostname-verification reporting and SSL factory contract =="
rg -n -C 5 'hostname-verification|requireHostnameValidation\s*\(' \
core/src/main/java core/src/test/javaLength of output: 40821
✏️ Learnings added
You are interacting with an AI system. |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
integration-tests/src/test/java/com/datastax/oss/driver/core/config/DriverConfigReportingCcmIT.java (1)
129-138: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy liftProve that the reporting row is the control connection.
This verifies cardinality, not identity: a regression that emits
DRIVER_CONFIGon one pool connection and omits it on the control connection still passes. Add a reliable control-connection marker/correlation before validating the payload, as the Simulacron test already does.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@integration-tests/src/test/java/com/datastax/oss/driver/core/config/DriverConfigReportingCcmIT.java` around lines 129 - 138, Update the reporting-row assertions in DriverConfigReportingCcmIT to correlate the DRIVER_CONFIG row with the control connection using the same reliable marker or correlation mechanism as the existing Simulacron test. Validate that the identified row is the control connection before calling assertDriverConfigPayload, while preserving the existing single-row cardinality check.core/src/test/java/com/datastax/oss/driver/internal/core/context/DefaultDriverConfigReporterTest.java (1)
321-352: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider single-argument override helpers to cut the
reporterWith(...)boilerplate.The 7-arg
reporterWith(defaults(map -> {}), mock(ExponentialReconnectionPolicy.class), mock(DefaultRetryPolicy.class), mock(NoSpeculativeExecutionPolicy.class), mock(DefaultLoadBalancingPolicy.class), mock(TimestampGenerator.class), Optional.empty())call is repeated ~15 times across this file, varying in exactly one argument. Thin wrappers (or a small builder) would make each test's intent obvious.♻️ Sketch
private DefaultDriverConfigReporter reporterWithReconnection(ReconnectionPolicy p) { return reporterWith( defaults(map -> {}), p, mock(DefaultRetryPolicy.class), mock(NoSpeculativeExecutionPolicy.class), mock(DefaultLoadBalancingPolicy.class), mock(TimestampGenerator.class), Optional.empty()); } // likewise reporterWithRetry / reporterWithSpecEx / reporterWithLb / reporterWithSsl🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@core/src/test/java/com/datastax/oss/driver/internal/core/context/DefaultDriverConfigReporterTest.java` around lines 321 - 352, Reduce repeated seven-argument setup in DefaultDriverConfigReporterTest by adding thin single-argument reporterWith helper methods for the varying policy/configuration dependencies, including reconnection policy and the analogous retry, speculative execution, load-balancing, and SSL cases. Update the affected tests, such as should_report_constant_reconnection_policy and should_report_custom_reconnection_policy, to use the appropriate helper while preserving their existing mocks and assertions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@core/src/test/resources/config/driver-config-report-v1.schema.json`:
- Around line 779-801: Update the consistency enum in the schema near the
consistency and serial-consistency properties to accept SERIAL and LOCAL_SERIAL
alongside the existing request consistency values. Keep the serial-consistency
property unchanged.
In
`@integration-tests/src/test/java/com/datastax/oss/driver/core/config/DriverConfigReportingAssertions.java`:
- Around line 29-43: Configure the shared OBJECT_MAPPER used by
assertDriverConfigPayload to enable
DeserializationFeature.FAIL_ON_TRAILING_TOKENS, ensuring readTree rejects valid
JSON followed by extra tokens while preserving the existing payload assertions.
---
Nitpick comments:
In
`@core/src/test/java/com/datastax/oss/driver/internal/core/context/DefaultDriverConfigReporterTest.java`:
- Around line 321-352: Reduce repeated seven-argument setup in
DefaultDriverConfigReporterTest by adding thin single-argument reporterWith
helper methods for the varying policy/configuration dependencies, including
reconnection policy and the analogous retry, speculative execution,
load-balancing, and SSL cases. Update the affected tests, such as
should_report_constant_reconnection_policy and
should_report_custom_reconnection_policy, to use the appropriate helper while
preserving their existing mocks and assertions.
In
`@integration-tests/src/test/java/com/datastax/oss/driver/core/config/DriverConfigReportingCcmIT.java`:
- Around line 129-138: Update the reporting-row assertions in
DriverConfigReportingCcmIT to correlate the DRIVER_CONFIG row with the control
connection using the same reliable marker or correlation mechanism as the
existing Simulacron test. Validate that the identified row is the control
connection before calling assertDriverConfigPayload, while preserving the
existing single-row cardinality check.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 74f644f4-90dd-478c-b6ac-fecd6b72bac0
📒 Files selected for processing (20)
core/pom.xmlcore/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.javacore/src/main/java/com/datastax/oss/driver/api/core/config/TypedDriverOption.javacore/src/main/java/com/datastax/oss/driver/api/core/ssl/ProgrammaticSslEngineFactory.javacore/src/main/java/com/datastax/oss/driver/api/core/ssl/SslEngineFactory.javacore/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.javacore/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverConfigReporter.javacore/src/main/java/com/datastax/oss/driver/internal/core/context/DriverConfigReporter.javacore/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/CassandraSchemaQueries.javacore/src/main/java/com/datastax/oss/driver/internal/core/ssl/DefaultSslEngineFactory.javacore/src/main/java/com/datastax/oss/driver/internal/core/ssl/SniSslEngineFactory.javacore/src/main/resources/reference.confcore/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.javacore/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.javacore/src/test/java/com/datastax/oss/driver/internal/core/context/DefaultDriverConfigReporterTest.javacore/src/test/resources/config/driver-config-report-v1.schema.jsonintegration-tests/src/test/java/com/datastax/oss/driver/core/config/DriverConfigReportingAssertions.javaintegration-tests/src/test/java/com/datastax/oss/driver/core/config/DriverConfigReportingCcmIT.javaintegration-tests/src/test/java/com/datastax/oss/driver/core/config/DriverConfigReportingSimulacronIT.javapom.xml
🚧 Files skipped from review as they are similar to previous changes (4)
- core/src/main/java/com/datastax/oss/driver/api/core/config/TypedDriverOption.java
- core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/CassandraSchemaQueries.java
- core/src/main/resources/reference.conf
- core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java
d6ac737 to
5e69715
Compare
There was a problem hiding this comment.
🧹 Nitpick comments (1)
core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java (1)
194-203: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winCentralize the ScyllaDB detection check
getShardingInfo() != nullis used here and again inCassandraSchemaQueries.shouldApplyUsingTimeout(). A shared helper would keep driver-config reporting andUSING TIMEOUTgating aligned if the detection logic changes later.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java` around lines 194 - 203, Centralize the ScyllaDB detection currently implemented by getShardingInfo() != null into a shared helper, then update the ProtocolInitHandler flow and CassandraSchemaQueries.shouldApplyUsingTimeout() to use it. Preserve the existing featureStore null handling and ensure both driver-config reporting and USING TIMEOUT gating rely on the same detection logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In
`@core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java`:
- Around line 194-203: Centralize the ScyllaDB detection currently implemented
by getShardingInfo() != null into a shared helper, then update the
ProtocolInitHandler flow and CassandraSchemaQueries.shouldApplyUsingTimeout() to
use it. Preserve the existing featureStore null handling and ensure both
driver-config reporting and USING TIMEOUT gating rely on the same detection
logic.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: af57b7b6-6e4e-44fd-9609-e06c113aa08b
📒 Files selected for processing (20)
core/pom.xmlcore/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.javacore/src/main/java/com/datastax/oss/driver/api/core/config/TypedDriverOption.javacore/src/main/java/com/datastax/oss/driver/api/core/ssl/ProgrammaticSslEngineFactory.javacore/src/main/java/com/datastax/oss/driver/api/core/ssl/SslEngineFactory.javacore/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.javacore/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverConfigReporter.javacore/src/main/java/com/datastax/oss/driver/internal/core/context/DriverConfigReporter.javacore/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/CassandraSchemaQueries.javacore/src/main/java/com/datastax/oss/driver/internal/core/ssl/DefaultSslEngineFactory.javacore/src/main/java/com/datastax/oss/driver/internal/core/ssl/SniSslEngineFactory.javacore/src/main/resources/reference.confcore/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.javacore/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.javacore/src/test/java/com/datastax/oss/driver/internal/core/context/DefaultDriverConfigReporterTest.javacore/src/test/resources/config/driver-config-report-v1.schema.jsonintegration-tests/src/test/java/com/datastax/oss/driver/core/config/DriverConfigReportingAssertions.javaintegration-tests/src/test/java/com/datastax/oss/driver/core/config/DriverConfigReportingCcmIT.javaintegration-tests/src/test/java/com/datastax/oss/driver/core/config/DriverConfigReportingSimulacronIT.javapom.xml
🚧 Files skipped from review as they are similar to previous changes (4)
- core/src/main/java/com/datastax/oss/driver/api/core/config/TypedDriverOption.java
- core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/CassandraSchemaQueries.java
- core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java
- core/src/main/resources/reference.conf
Fills in the full DRIVER_CONFIG JSON report in the approved v2
cross-driver schema shape, replacing the stage-1 {"version":1}
placeholder. All groups are populated from Configuration and
Policies on each control-connection init.
Adds public getters to DCAwareRoundRobinPolicy and
RackAwareRoundRobinPolicy needed to report node-location-preference
and dc-failover, and makes PagingOptimizingLoadBalancingPolicy
implement ChainableLoadBalancingPolicy so the reporter can unwrap the
LB policy Cluster.Manager wraps at runtime.
Adds a JSON-Schema conformance test suite (mirroring the 4.x sibling
PR scylladb#968): the normative schema is shipped as a test resource and
validated via com.networknt:json-schema-validator (pinned to 1.5.x,
the last line still targeting Java 8), covering every discriminated-
union branch and optional group the 3.x reporter can emit, plus a
negative test proving additionalProperties=false is enforced.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fills in the full DRIVER_CONFIG JSON report in the approved v2
cross-driver schema shape, replacing the stage-1 {"version":1}
placeholder. All groups are populated from Configuration and
Policies on each control-connection init.
Adds public getters to DCAwareRoundRobinPolicy and
RackAwareRoundRobinPolicy needed to report node-location-preference
and dc-failover, and makes PagingOptimizingLoadBalancingPolicy
implement ChainableLoadBalancingPolicy so the reporter can unwrap the
LB policy Cluster.Manager wraps at runtime.
Adds a JSON-Schema conformance test suite (mirroring the 4.x sibling
PR scylladb#968): the normative schema is shipped as a test resource and
validated via com.networknt:json-schema-validator (pinned to 1.5.x,
the last line still targeting Java 8), covering every discriminated-
union branch and optional group the 3.x reporter can emit, plus a
negative test proving additionalProperties=false is enforced.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fills in the full DRIVER_CONFIG JSON report in the approved v2
cross-driver schema shape, replacing the stage-1 {"version":1}
placeholder. All groups are populated from Configuration and
Policies on each control-connection init.
Adds public getters to DCAwareRoundRobinPolicy and
RackAwareRoundRobinPolicy needed to report node-location-preference
and dc-failover, and makes PagingOptimizingLoadBalancingPolicy
implement ChainableLoadBalancingPolicy so the reporter can unwrap the
LB policy Cluster.Manager wraps at runtime.
Adds a JSON-Schema conformance test suite (mirroring the 4.x sibling
PR scylladb#968): the normative schema is shipped as a test resource and
validated via com.networknt:json-schema-validator (pinned to 1.5.x,
the last line still targeting Java 8), covering every discriminated-
union branch and optional group the 3.x reporter can emit, plus a
negative test proving additionalProperties=false is enforced.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fills in the full DRIVER_CONFIG JSON report in the approved v2
cross-driver schema shape, replacing the stage-1 {"version":1}
placeholder. All groups are populated from Configuration and Policies
when the report is built, i.e. once per Cluster as it initializes.
Adds public getters to DCAwareRoundRobinPolicy and
RackAwareRoundRobinPolicy needed to report node-location-preference
and dc-failover, and makes PagingOptimizingLoadBalancingPolicy
implement ChainableLoadBalancingPolicy so the reporter can unwrap the
LB policy Cluster.Manager wraps at runtime.
Adds a JSON-Schema conformance test suite (mirroring the 4.x sibling
PR scylladb#968): the normative schema is shipped as a test resource and
validated via com.networknt:json-schema-validator (pinned to 1.5.x,
the last line still targeting Java 8), covering every discriminated-
union branch and optional group the 3.x reporter can emit, plus a
negative test proving additionalProperties=false is enforced.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
5e69715 to
cfda714
Compare
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (2)
core/src/test/java/com/datastax/oss/driver/internal/core/context/DefaultDriverConfigReporterTest.java (1)
930-966: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoffConsider a small builder for the reporter fixtures.
The 7-argument and 8-argument
reporterWithcalls repeat across about twenty tests, and each call varies only one argument. A builder that starts from the default policy set and overrides one collaborator would remove that repetition and make each test state its single variable.Example shape:
private final class ReporterBuilder { private DriverExecutionProfile profile = defaults(map -> {}); private ReconnectionPolicy reconnection = mock(ExponentialReconnectionPolicy.class); // ... remaining collaborators with the same defaults as defaultsReporter() ReporterBuilder reconnection(ReconnectionPolicy p) { this.reconnection = p; return this; } DefaultDriverConfigReporter build() { /* wire the mock context */ } }Each test then reads
builder().reconnection(mock(ConstantReconnectionPolicy.class)).build().🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@core/src/test/java/com/datastax/oss/driver/internal/core/context/DefaultDriverConfigReporterTest.java` around lines 930 - 966, Refactor the repeated reporter fixture setup around the overloaded reporterWith methods into a small ReporterBuilder that initializes the same defaults as defaultsReporter() and exposes fluent overrides for individual collaborators, including the programmatic local datacenter. Update the affected tests to build reporters by overriding only the variable under test, while preserving the existing mock context wiring and behavior.core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverConfigReporter.java (1)
152-152: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider package-private visibility for
buildJson.
DefaultDriverConfigReporterTestis in the same package,com.datastax.oss.driver.internal.core.context. Package-private visibility therefore supports the test override without adding a subclass extension point that the javadoc must then qualify with thread-safety caveats.♻️ Proposed change
- protected String buildJson(boolean scyllaDb) { + String buildJson(boolean scyllaDb) {If a production subclass hook is intended, keep
protectedand disregard this suggestion.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverConfigReporter.java` at line 152, Change the buildJson method in DefaultDriverConfigReporter from protected to package-private visibility, allowing DefaultDriverConfigReporterTest to override it within the same package without exposing a production subclass extension point.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@core/src/main/java/com/datastax/oss/driver/api/core/config/OptionsMap.java`:
- Line 403: Restore opt-in driver configuration reporting by setting
TypedDriverOption.DRIVER_CONFIG_REPORTING_ENABLED to false in OptionsMap. Update
DriverConfigReportingSimulacronIT at lines 53-60 and 145-160 to assert and
enable reporting explicitly as needed, and update upgrade_guide/README.md lines
40-44 to document that reporting is disabled by default.
In
`@core/src/main/java/com/datastax/oss/driver/internal/core/context/DriverConfigReporter.java`:
- Around line 23-33: Change driver-config reporting defaults from enabled to
disabled across OptionsMap.fillWithDriverDefaults and
DefaultDriverConfigReporter, then update the corresponding documentation and
tests to reflect false as the default. Preserve explicit opt-in behavior,
ensuring default sessions do not send the DRIVER_CONFIG startup option.
In
`@integration-tests/src/test/java/com/datastax/oss/driver/core/config/DriverConfigReportingCcmIT.java`:
- Around line 133-142: Update the assertions in DriverConfigReportingCcmIT to
identify the DRIVER_CONFIG row by matching its connection local address and port
against the control connection, using the existing control-connection details
and clientOptions helpers. Preserve the single-row and payload assertions, but
ensure a pooled connection cannot satisfy the test.
In `@upgrade_guide/README.md`:
- Around line 42-44: Update the fenced configuration block in the README to
specify the HOCON language identifier, changing the opening fence to use hocon
while preserving the existing configuration content.
---
Nitpick comments:
In
`@core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverConfigReporter.java`:
- Line 152: Change the buildJson method in DefaultDriverConfigReporter from
protected to package-private visibility, allowing
DefaultDriverConfigReporterTest to override it within the same package without
exposing a production subclass extension point.
In
`@core/src/test/java/com/datastax/oss/driver/internal/core/context/DefaultDriverConfigReporterTest.java`:
- Around line 930-966: Refactor the repeated reporter fixture setup around the
overloaded reporterWith methods into a small ReporterBuilder that initializes
the same defaults as defaultsReporter() and exposes fluent overrides for
individual collaborators, including the programmatic local datacenter. Update
the affected tests to build reporters by overriding only the variable under
test, while preserving the existing mock context wiring and behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: e40d8974-45a6-4285-bc5b-2d6d30c334dc
📒 Files selected for processing (26)
core/pom.xmlcore/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.javacore/src/main/java/com/datastax/oss/driver/api/core/config/OptionsMap.javacore/src/main/java/com/datastax/oss/driver/api/core/config/TypedDriverOption.javacore/src/main/java/com/datastax/oss/driver/api/core/ssl/ProgrammaticSslEngineFactory.javacore/src/main/java/com/datastax/oss/driver/api/core/ssl/SslEngineFactory.javacore/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.javacore/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverConfigReporter.javacore/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.javacore/src/main/java/com/datastax/oss/driver/internal/core/context/DriverConfigReporter.javacore/src/main/java/com/datastax/oss/driver/internal/core/context/StartupOptionsBuilder.javacore/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/CassandraSchemaQueries.javacore/src/main/java/com/datastax/oss/driver/internal/core/ssl/DefaultSslEngineFactory.javacore/src/main/java/com/datastax/oss/driver/internal/core/ssl/SniSslEngineFactory.javacore/src/main/resources/reference.confcore/src/test/java/com/datastax/dse/driver/internal/core/context/DseStartupOptionsBuilderTest.javacore/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.javacore/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.javacore/src/test/java/com/datastax/oss/driver/internal/core/context/DefaultDriverConfigReporterTest.javacore/src/test/java/com/datastax/oss/driver/internal/core/context/StartupOptionsBuilderTest.javacore/src/test/resources/config/driver-config-report-v1.schema.jsonintegration-tests/src/test/java/com/datastax/oss/driver/core/config/DriverConfigReportingAssertions.javaintegration-tests/src/test/java/com/datastax/oss/driver/core/config/DriverConfigReportingCcmIT.javaintegration-tests/src/test/java/com/datastax/oss/driver/core/config/DriverConfigReportingSimulacronIT.javapom.xmlupgrade_guide/README.md
🚧 Files skipped from review as they are similar to previous changes (13)
- core/src/main/java/com/datastax/oss/driver/api/core/config/TypedDriverOption.java
- core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java
- core/src/main/java/com/datastax/oss/driver/internal/core/ssl/DefaultSslEngineFactory.java
- core/src/main/java/com/datastax/oss/driver/api/core/ssl/ProgrammaticSslEngineFactory.java
- integration-tests/src/test/java/com/datastax/oss/driver/core/config/DriverConfigReportingAssertions.java
- core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/CassandraSchemaQueries.java
- core/pom.xml
- core/src/main/java/com/datastax/oss/driver/internal/core/ssl/SniSslEngineFactory.java
- core/src/main/java/com/datastax/oss/driver/api/core/ssl/SslEngineFactory.java
- core/src/test/resources/config/driver-config-report-v1.schema.json
- pom.xml
- core/src/main/resources/reference.conf
- core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java
cfda714 to
312b522
Compare
|
Dispositions for the two nitpicks:
|
|
On the two nitpicks from the last CodeRabbit pass: |
The cross-driver design doc revised v1 in place while this PR was in review.
The change is breaking but not a version bump — `$id` and `version` both stay
at 1, which is only safe because v1 has not shipped in a release yet.
The flat envelope becomes three groups — `connection`, `control-plane` and
`query` — with `additionalProperties: false` at the root, so every former
top-level group moves under one of them:
socket -> connection.socket (now required)
reconnection-policy -> connection.reconnection.policy
tls -> connection.tls (optional)
retry-policy -> query.retry.policy
load-balancing-policy -> query.load-balancing.policy
speculative-execution-policy -> query.speculative-execution.policy
query-defaults -> query.defaults
control-plane.system-queries.timeout
-> control-plane.queries.system.timeout
control-plane.schema-agreement.timeout-ms
-> control-plane.schema.agreement.timeout-ms
Beyond the re-homing, four changes alter what is emitted:
* `tls.enabled` and `adaptive-ordering.enabled` are gone — presence of each
group is what reports it as on, so both are omitted rather than emitted
with a false flag. `adaptive-ordering` also now requires a non-empty
signal list, which rules out the old empty-array form.
* `dc-failover` is renamed `fallback-to-non-preferred-nodes`.
* `query.defaults.request` is optional, so a disabled request timeout is
reported by omission. That was one of the two fields with no schema-valid
form; only `connection.requests.in-flight.max` is left.
* `node-location-preference` now has two homes, and they are filled
differently. `computeNodeDistance` derives node distance from the local DC
alone — a node outside it is IGNORED, and an IGNORED node gets no pool — so
the datacenter genuinely scopes connections and goes under
`connection.node-preference`. The rack never reaches that method; it only
reorders replicas at the head of a query plan, so the full preference
belongs under `query.load-balancing.node-preference` and the connection
group carries the datacenter half alone.
Also addresses three review comments from @dkropachev:
* Hostname verification is read from the engine factory the active
`JdkSslHandlerFactory` wraps, not from `getSslEngineFactory()`. The two can
differ, and going through the context could be the first caller to resolve
a `LazyReference` nothing uses — reading keystore files on a Netty event
loop, and costing the whole report if it throws.
* Reconnection delays are read from the running policy instead of the
profile, since both built-ins latch them at construction. This also makes
the schema's new `max-ms >= base-ms` invariant hold for free.
* The local DC the policy has already inferred is now reported, via the
schema's `dc-auto.local-dc` and `rack-auto.inferred-local-dc` slots. It is
null on the first control connection and resolved on every reconnect,
which is exactly the distinction those fields exist to draw.
The five JSON syntax errors in the doc's schema block (four stray commas and
two missing ones) are fixed in the shipped resource, which is otherwise a
verbatim copy so it stays auditable against the doc.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The cross-driver design doc revised v1 in place again while this PR was in review. The delta from the previous revision is one optional key: `max-retries` is now permitted on the `standard-error-aware`, `never`, `downgrading-consistency` and `custom` retry-policy variants. `simple` already required it and `fallthrough` deliberately has no such key. Adding a key is backward-compatible per the spec's own evolution rule, so `$id` and `version` both stay at 1. The shipped schema resource is again byte-identical to the doc's schema block, which the doc revision also fixed the JSON syntax of (those five errors were already corrected here). Nothing new is emitted. The key reports a retry limit taken *from configuration*, and Java has no such option — no `max-retries` equivalent exists in `reference.conf`, `DefaultDriverOption` or `TypedDriverOption`, which is why the doc's own per-driver mapping table lists it as n/a for java. What the two built-ins have instead are per-error-type rules hardcoded in Java: a single attempt for read timeouts, write timeouts and unavailable, but an unbounded walk down the query plan for aborted requests and error responses. No single number describes that, so reporting one would be worse than omitting it. A custom policy cannot be introspected for a limit either. The three retry-policy tests now pin that omission, so the new schema slot is not later filled with a hardcoded count. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A field-by-field audit of the shipped v1 schema against the code that actually consumes each option found 30 of the 34 emitted fields exact. Three were not, and are fixed here; the other nine findings are approximations the schema shape cannot avoid, or items for the schema owner, and are listed in the PR description rather than acted on. 1. `InternalError` was caught around the whole report build. It is a `VirtualMachineError`, so that also swallowed one raised by anything else on the path -- config access, a user policy, Jackson -- rather than only the documented `getSimpleName()` JDK edge case it was added for. The top-level catch is now `RuntimeException`, and the `InternalError` catch sits next to the `getSimpleName()` call it guards, behind a package-private `simpleName` seam so the branch stays testable (no class a test can declare provokes the error). Raised in review. 2. `query.defaults.serial-consistency` was reported verbatim. `basic.request.serial-consistency` is an unvalidated string that nothing checks until the first conditional statement runs (`Conversions`), while the schema's enum admits only `SERIAL` and `LOCAL_SERIAL` -- so a session configured with anything else produced a document that fails validation as a whole. Unlike its *required* sibling `consistency`, this key is optional, so the reporter's own documented omission principle applies here and simply was not being followed. Now emitted only for the two schema members, with the class javadoc explaining why this one is not a third known gap. 3. `dc-auto` was fabricated for policies that never infer a datacenter. The preference was omitted only for the exact `BasicLoadBalancingPolicy`; every custom policy with no configured DC still reported `dc-auto`, which claims a datacenter *will* be settled on -- something `LoadBalancingPolicy` nowhere requires an implementation to do. Now reported when a DC is configured, or the policy has already resolved one (evidence, read through `instanceof`, so a subclass counts), or its exact class is one of the four built-ins known to infer; otherwise omitted from both parents, where the group is optional. This subsumes the old exclusion including its rack-only case: no built-in looks for a rack before it knows a datacenter. Raised in review. Test suite goes from 96 to 101 cases in `DefaultDriverConfigReporterTest`: the `getSimpleName()` fail-safe test is reworked to assert the binary-name fallback rather than a dropped report, plus an anonymous-policy naming case, an out-of-enum and a `LOCAL_SERIAL` serial-consistency case, a DC-agnostic custom policy, and a non-inferring policy that has nonetheless resolved a DC. The default-report test now also pins `serial-consistency`. Full `core` suite green (3889 tests). Deliberately not folded into the commits that introduce this code, unlike the previous rounds: a review is pending, and rewriting five SHAs mid-pass would throw away the reviewer's "what changed since I last looked" diff. The three regions all originate in commit `f445494`, so they can be folded on request. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The normative document has been revised again. Four changes reach this
driver:
- connection.requests.in-flight.max drops its 1..32767 range for the
shared positiveInteger definition;
- query.defaults.consistency gains SERIAL and LOCAL_SERIAL;
- query.defaults.client-timestamps and tls.hostname-verification
become optional, absent when the behavior is unknown;
- query.retry forbids a backoff on a fallthrough policy, which is
vacuous here since the reporter emits neither.
The first two close both documents this reporter knowingly emitted out
of schema. What is left of each is much narrower: in-flight.max must
still be positive and nothing in the driver enforces that, and a
consistency name outside the enum now needs a custom
ConsistencyLevelRegistry, since the built-in load balancing policies
reject anything the default registry does not know before a report is
ever built. The class javadoc and the two tests that pinned the old
bounds say so; a third test pins the serial levels as now valid.
The vendored resource is again byte-identical to the document's schema
block. Two description rewrites come with it, one of which fixes the
dangling ../../node-preferences pointer this branch reported upstream.
Also assert the absent retry backoff on the group rather than on the
policy node, which is where the schema puts it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The accessor this branch added returns a plain boolean, defaulting to false, so a custom factory that does validate host names is reported as one that does not. That default was chosen because the schema had no way to say "unknown" — tls.hostname-verification was required. It is optional now, and absent is defined to mean exactly that, so the accessor can stop guessing: it returns an Optional, empty by default, and the reporter omits the key rather than inventing a boolean for a security control it cannot read. The three built-in factories all know their own answer and keep reporting it. The other unknown case is unchanged in substance and now says so the same way: when the handler factory in force is not the driver's own JdkSslHandlerFactory, host name validation is a property of a JDK SSLEngine that is not on that path, so the key is omitted instead of reported false. The method is new in this branch and unreleased, so the signature change breaks nothing; revapi diffs against the last published release, where it does not exist. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Same defect as the SSL accessor, and the same fix. isClientSide() defaulted to true, on the grounds that assigning timestamps client-side is the interface's contract — but it is only the usual contract: returning NO_DEFAULT_TIMESTAMP from next() and letting the coordinator assign is documented and legal, and nothing short of calling next() can detect it. So the default reported every custom generator as client-side, which is the over-claim moving the check off the class was meant to remove; it only moved. query.defaults.client-timestamps is optional now, with absent defined as unknown, so the accessor returns an Optional and defaults to empty. Both monotonic built-ins always assign the timestamp themselves, so one override on their shared base covers them, and the server-side one keeps reporting false. The test that pinned the server-side path relied on Mockito answering an unstubbed boolean with false; it stubs explicitly now, since with an Optional return that silence would have turned it into an omission test. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Twelve of this class's twenty-five profile reads used the no-fallback getters, which throw when an option is absent. One throw is caught by the single top-level handler, so a config source that omitted any one of them dropped all thirty-odd fields behind one warning — and the seven reads that did pass a fallback looked arbitrary next to them. They are not arbitrary any more, because the schema decides. Where the field or its enclosing group is optional, the fallback is the same "disabled" sentinel that already omits it, so an undefined option is reported exactly the way a disabled one is and no new branch is needed: an undefined page size reads as unbounded, an undefined timeout as off, an undefined max-executions drops the speculative-execution group. Where the field is required, omitting it would invalidate the document, so the fallback is the value reference.conf documents. That covers all twenty-five reads — nineteen with a fallback, six behind an isDefined guard — and makes the invariant statable: no missing option costs more than the field it describes. Two of the five required-field fallbacks cannot fire anyway, since ChannelFactory and the built-in load balancing policies read those options before any report is built; the javadoc says which and why. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
fallback-to-non-preferred-nodes was read off max-nodes-per-remote-dc alone, but BasicLoadBalancingPolicy#maybeAddDcFailover appends remote nodes to a query plan only when that option is positive AND the policy has a local DC to treat as preferred. So a config that changed nothing but the option reported failover as on for a session where no remote node is ever appended — and the key is defined in terms of leaving the node preference, which such a report does not even carry. The second term is the predicate the reporter already computes for the node-preference groups, non-null exactly when a DC is configured, has already been resolved by the policy, or the policy is one of the four built-ins known to infer one. Reused rather than restated, so no policy logic is duplicated. One term is still missing on purpose: maybeAddDcFailover also consults isDcFailoverAllowedForRequest, which is false for a DC-local consistency while allow-for-local-consistency-levels is off. That is a per-request decision a statement can override, and re-deriving it in a diagnostic would duplicate exactly what this commit avoids. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…bsent The driver declares Jackson as a required dependency but documents that it can be excluded when unused (manual/core/integration), and enforces that for Insights by checking for it before building the lifecycle listener. Driver config reporting is a third Jackson user, it ships enabled, and it runs on the connection initialization path -- with no such check. On a classpath without Jackson, merely linking DefaultDriverConfigReporter raises NoClassDefFoundError. That is an Error rather than an exception, and it is raised while resolving the class rather than from any method it declares, so neither the reporter's own fail-safe nor ProtocolInitHandler can contain it: every control connection fails, and the session cannot be built at all. A documented, supported configuration went from "the report is skipped" to "the driver does not work", which is the opposite of the invariant the reporter is written around. So pick the implementation up front, the way buildLifecycleListeners() already does, and fall back to a no-op reporter that names no Jackson type anywhere -- one reference would make loading it fail for exactly the deployments it exists to serve. Logged unconditionally, unlike the Insights equivalent: nobody opted in to reporting, so nobody would think to look for a message saying it is off. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
DefaultSession's init eagerly forces every user-facing policy before opening any connection, so that a bad configuration fails the session rather than each connect. The config reporter was the one component the reporting path touches that was left out, which had a second consequence: a Netty event loop became the first thread to load DefaultDriverConfigReporter and, with it, Jackson -- reading jars from an event loop, mid-Startup. Adding it to that list costs nothing (the reporter only stores the context) and makes the ordering the reporter's javadoc already relied on true by construction rather than by coincidence. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The class javadoc, the comment in requests(), and the test that pins the
behavior all said a non-positive max-requests-per-connection "starts a
session -- one that cannot acquire a stream id" and is then reported. It
does not start one:
- a negative value makes StreamIdGenerator's BitSet throw while
ChannelFactory is still building the channel;
- zero leaves no stream id at all, so ChannelHandlerRequest fails the
control connection's own OPTIONS on preAcquireId, before Startup is
composed and long before anything asks for a report.
So of the two required fields the schema constrains more tightly than the
option behind them, only query.defaults.consistency is reachable through a
running driver -- and even that needs a custom ConsistencyLevelRegistry. The
same configuration would also drive orphaned.max negative, which is a second
reason to read this as one unreachable shape rather than one field's gap.
Behavior is unchanged: the value is still passed through, and still pinned,
so it stays defined if the driver ever stops failing that early. Only the
claims about it change.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
F6 in the value audit covers a configured rack reported for a policy that ignores it, and X3 covers not fabricating dc-auto for a policy that may never infer one. Neither covers the case in between: a configured *datacenter* reported for a custom load balancing policy. Both parents claim an effect only the built-ins produce. connection's node-preference says the datacenter decides which nodes hold a pool, which holds because BasicLoadBalancingPolicy#computeNodeDistance makes an out-of-DC node IGNORED; query.load-balancing's says it scopes routing. A custom LoadBalancingPolicy computes distance itself and need not read basic.load-balancing-policy.local-datacenter or withLocalDatacenter at all, so it may honor neither. Kept as-is, on the same grounds as F6 -- hiding a setting the operator really did make is the worse failure mode -- but the asymmetry with X3 is worth stating where the decision lives: nothing is inferred on a custom policy's behalf, while what was configured is passed through. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The conformance suite covers every branch of every discriminated union the
reporter can emit, except one: speculative-execution's custom variant. The
constant variant is validated, and the subclass-reported-as-custom test
asserts the shape but never runs it past the schema.
It passes as written -- {type, name} with additionalProperties: true is valid
-- so this closes coverage rather than fixing anything. Worth having because
the enclosing group is optional and behaves differently per branch: it is
dropped entirely for NoSpeculativeExecutionPolicy but kept here.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
connectionsPerShard() factored out arithmetic that initialize() and resize() each spelled out, which is a fine change but has nothing to do with driver config reporting. Reverted to keep the branch to its subject. ProtocolFeatureStore#getNodeShardingInfo and the DriverChannel simplification stay: the reporter needs the former, and the latter is its call site. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
tls() restated, at length, the argument buildJson()'s javadoc already makes for reading the engine factory off the handler in force rather than through the context. Replaced with a pointer, leaving the javadoc as the single home for it and the method comment to explain only what it does. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…g with query.speculative-execution.policy read max-executions and delay-ms from the default profile at report time, while the policy latched both into final fields when the context built it -- and advanced.speculative-execution-policy is documented as not modifiable at runtime. After a configuration reload the report published numbers no request executes with: a max-executions lowered to 1 dropped the whole group, claiming no speculative execution while the policy still fired three, and a negative delay put a value the schema's nonNegativeInteger rejects into an otherwise valid document. A context that overrides buildSpeculativeExecutionPolicies() reaches the same divergence with no reload at all. Both values now come off the running ConstantSpeculativeExecutionPolicy, the way the reconnection policy already is, so that policy's own constructor validation (max-executions >= 1, delay >= 0) keeps the report inside the schema's ranges by construction. Raised by @dkropachev on the 3.x port (scylladb#974), where the same two fields were misreported as a custom policy. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The two values a load balancing policy captures at construction were still being read from the profile at report time, so a configuration reload could put the report and the running policy out of step: * `adaptive-ordering` came from `LOAD_BALANCING_POLICY_SLOW_AVOIDANCE`, while `DefaultLoadBalancingPolicy` latches it into a final field. Reloading the option to false dropped the group while the policy kept reordering replicas; reloading it the other way claimed an ordering that was never applied. * the first term of `fallback-to-non-preferred-nodes` came from `LOAD_BALANCING_DC_FAILOVER_MAX_NODES_PER_REMOTE_DC`, latched the same way by `BasicLoadBalancingPolicy`, so the report could claim failover for a policy built with none, or deny it for one that appends remote nodes on every plan. Both now read the accessors on the running instance, which is what the report claims to describe -- the same source-of-truth fix already applied to the reconnection delays and the speculative-execution parameters. The class javadoc listed these two as the standing exception; it no longer needs to. Raised by @dkropachev for adaptive ordering. The DC-failover term is the same defect one field over and is fixed alongside it, since leaving one of a named pair is worse than fixing neither. The third condition on `maybeAddDcFailover` is untouched -- that is a per-request decision, not a stale read. Tests pin each value with the profile set one way and the policy the other. The mocked policies now go through a helper that stubs what they latched, since a bare mock reports every built-in as having neither. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`control-plane.queries.system.timeout.server-side-ms` was emitted only when the control connection's sharding information said the peer was ScyllaDB, mirroring `CassandraSchemaQueries.shouldApplyUsingTimeout()` -- the same check that decides whether a `USING TIMEOUT` clause built from this option reaches the wire at all. That made the field describe the effect rather than the setting. Per @dkropachev, who owns the schema, it carries what is configured: `advanced.metadata.schema.request-timeout` is known before the driver connects, and `pool.shard-aware.enabled` in the same report already reads as intent, with its schema description saying so outright. So the gate is gone and the value is emitted whenever it is positive. The cost is that an operator on generic Cassandra now reads a server-side timeout nothing will enforce -- worth the matching "reports configuration intent" sentence in the schema, raised upstream. That was the reporter's only backend-conditional value, so the `NodeShardingInfo` argument existed for it alone: it is dropped from `DriverConfigReporter`, both implementations, `buildJson()` and `ProtocolInitHandler`'s `STARTUP` case, along with the cross-reference comment in `CassandraSchemaQueries`. The report now depends on nothing the peer said. The two `ProtocolInitHandlerTest` cases that asserted the sharding information reached the reporter go with the argument; `ShardingInfoTest` still covers the parsing itself. The CCM integration test loses its backend-conditional branch -- the payload now reads identically on both backends. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`trimmedToNull` stripped surrounding whitespace before reporting `local-dc` and
`local-rack`, so a configured `" dc1 "` was published as `dc1`. The helpers that
feed the running policy -- `OptionalLocalDcHelper`, `OptionalLocalRackHelper` --
do no such thing: they hand the string over verbatim and match it against a
node's datacenter with `Objects.equals`, so that configuration matches no node at
all. The report hid exactly the typo an operator opens it to find.
The justification for trimming did not survive being checked. `nonEmptyString` is
`{"type":"string","minLength":1}`, so the padded form is valid to emit as is; the
constraint only ever covered the *blank* case, where there is genuinely nothing
to report -- `""` is not a `nonEmptyString`, and a `type: "dc"` preference with
the key omitted is invalid too. So `blankToNull` passes the value through and
maps only a blank one to "no preference", which stays documented as lossy.
Raised by @dkropachev. His alternative -- normalizing the runtime helpers so that
`" dc1 "` matches -- is declined here: that changes routing for existing users,
which does not belong in a change to a diagnostic.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
4ddf4ca to
7bfc53c
Compare
`nodeLocation()` read `local-datacenter` from the profile and consulted `BasicLoadBalancingPolicy.getLocalDatacenter()` only when nothing was configured. The context builds the load balancing policy once, in a `LazyReference`, so a profile reloaded from dc1 to dc2 leaves the policy still treating dc1 as local -- an out-of-dc1 node stays IGNORED and gets no pool -- while both `node-preference` groups published dc2. The report described a locality no request was routed by. The resolved value now wins wherever the policy has one. Whether anything was configured decides only which schema slot it occupies: a datacenter the policy took from an earlier generation of the configuration is still explicitly configured, merely stale, so it stays `type: "dc"` rather than being demoted to `dc-auto`. Exactly one of the configured and inferred forms survives, which keeps the schema's constraint on the pair true by construction. Before initialization -- the state the very first control connection sends STARTUP in -- nothing is resolved and the configured value stands alone, unchanged, and a custom policy exposes no accessor so what it was configured with is still passed through. This also drops the re-derivation of `OptionalLocalDcHelper`'s precedence for every state except the pre-init one, closing an item the design doc had carried since the first draft as a duplication concern -- which is why it was never read as the staleness bug it also was. Found by sweeping every option the reporter reads against whether its consumer stores the value, rather than by review; it is the seventh and last instance of that defect in this class. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Four review rounds produced findings that were nearly all instances of three rules, and the rules were nowhere written down -- so each instance had to be raised, argued and fixed on its own. They are now on the class, where someone adding a field will see them: * Source. Report the object in force, not the configuration it was built from. A policy or factory that captures an option once keeps using that value for the life of the session, since the context holds every one of them in a once-built LazyReference; the profile is then the wrong source. Six fields already read their instance for this reason and the paragraph now names all of them, along with the question to ask of a new one. * Range. Every value the option legally accepts must land inside the schema's constraint or take the omission route -- disabled, negative, sub-millisecond against a whole-millisecond field, undefined. With the trap that produced the max-executions off-by-one: the driver's units and the schema's need not agree, so compare the definitions rather than the names. * Warrant. Do not assert a property the implementation does not guarantee. This one is a judgement call, and the javadoc says so; what it can pin down is the line this class has consistently drawn -- nothing is inferred on a third party's behalf, but what was explicitly configured is passed through even where the component in force may ignore it. Documentation only. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both were asked on a sibling implementation of this same schema and neither
answer existed in 4.x, so both would have cost a review round to establish.
`max-retries` was asked for on the 3.x port as an unconditional 1, and declined
there. 4.x is identical and the existing note ("no single number describes that")
does not show why, since 1 looks correct: DefaultRetryPolicy really does cap
onReadTimeout, onWriteTimeout and onUnavailable at retryCount == 0. What it omits
is that CqlRequestHandler reaches onErrorResponseVerdict only for an idempotent
statement and then never checks the count, so the same policy bounds a
non-idempotent request at 1 and an idempotent one at the length of the query
plan. Idempotence is per statement, which a session-level report cannot know.
`connection.node-preference` was the only comment on the gocql PR, asking that it
be populated from introspectable location filters such as DataCenterHostFilter.
Java's analogue is basic.load-balancing-policy.evaluator.class, and it deserves a
note for a stronger reason than the absence of one: computeNodeDistance consults
the evaluator before the datacenter and returns its verdict directly, so it can
leave an in-DC node IGNORED and without a pool -- weakening the same claim the
group already qualifies for custom policies. Nothing is reported for it and
nothing can be: the option names a user-supplied class and the driver ships no
location-based evaluator of its own, so there is no datacenter to read out.
Documentation only.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Fills in the full DRIVER_CONFIG JSON report in the normative cross-driver
schema shape, replacing the stage-1 {"version":1} placeholder. All groups
are populated from Configuration and Policies when the report is built,
i.e. once per Cluster as it initializes.
Everything hangs off three groups. connection carries the connect/read
timeouts, the per-connection request capacity, the pool, the socket
options, the reconnection policy and -- only when TLS is on -- tls.
control-plane carries the system-query and schema-agreement timeouts.
query carries the per-request defaults plus the three policies acting on
a query: retry, load-balancing (with the node preference beside it) and,
when configured, speculative-execution.
The schema reports the node preference in two places and 3.x fills both
from the same policy chain: query.load-balancing.node-preference for what
a query is routed by, connection.node-preference for the part of the
cluster the driver holds connections to. One LoadBalancingPolicy decides
both, since distance(Host) governs whether a host is pooled at all, so
the connection key carries the datacenter half alone. A rack-aware
policy's distance() returns REMOTE, never IGNORED, for a local-datacenter
host in another rack, so those hosts are still pooled and the rack scopes
no pooling at all; the datacenter does, a host outside the preferred one
being IGNORED unless the policy is configured to use hosts there, and an
ignored host gets no pool.
token-aware is the only built-in load balancing shape the schema defines,
so every other built-in policy -- a bare DCAwareRoundRobinPolicy,
RoundRobinPolicy, WhiteListPolicy -- is reported as custom with its class
name, which identifies it but carries none of the normalized flags; its
datacenter and rack still show up in query.load-balancing.node-preference.
A token-aware chain reports load-distribution from its replica ordering
(RANDOM, the 3.x default, is "shuffle"; TOPOLOGICAL is "replica-set";
NEUTRAL keeps the child's plan order, so "round-robin").
fallback-to-non-preferred-nodes is true whenever the policy can reach a
node outside the preference reported beside it. For DCAwareRoundRobin
that means used-hosts-per-remote-DC, since the preference is the
datacenter. RackAwareRoundRobin reports a rack, and the other racks of
its local datacenter are outside that yet are the second tier of every
query plan -- distance() returns REMOTE, not IGNORED, for them -- so it
is always true there, remote datacenter hosts or not.
adaptive-ordering has no "enabled" flag and cannot carry an empty signal
list, so it is reported only when a LatencyAwarePolicy is in the chain --
latency being the only runtime observation a 3.x policy can reorder
candidates on. tls likewise has no "enabled" flag: the group's presence
is what says TLS is on.
Where a configured value falls outside what the schema can express, an
optional key or group is omitted rather than emitted as a value the
schema rejects: a disabled connect timeout, a disabled read timeout (all
three of connection.read, control-plane.queries.system.timeout
.client-side-ms and query.defaults.request), a negative SO_LINGER, a
non-positive socket buffer size, an unbounded page size, and a default
serial consistency level that is not serial -- QueryOptions, unlike
Statement, does not check that one. Two optional bounds are omitted for
the opposite reason -- 3.x has no such bound to report at all:
connection.reconnection.policy.max-attempts, since its reconnection
policies retry forever (the maxAttempts field ExponentialReconnection
Policy carries is an overflow guard on the doubling, not a give-up
bound: past it nextDelayMs() keeps returning maxDelayMs), and
query.retry.policy.max-retries, since no
single number describes a 3.x retry policy. Both built-ins are
parameterless singletons, and while they stop after one attempt on a
read timeout, a write timeout or an unavailable error -- all three
sharing one counter, so one retry between them rather than one each --
onRequestError leaves nbRetry unread and keeps trying the next host
until the query plan runs out. Which of the two applies is decided per
statement rather than by configuration: RequestHandler only consults
onRequestError and onWriteTimeout for an idempotent statement, so the
same policy bounds a non-idempotent request at one retry and an
idempotent one at the length of the query plan, and setIdempotent
overrides the reported query.defaults.idempotence per statement.
Two more keys are omitted for a third reason -- the schema admits only a
boolean, and 3.x cannot observe which one applies.
query.defaults.client-timestamps is false for ServerSideTimestamp
Generator, whose next() always returns Long.MIN_VALUE, and true for an
AbstractMonotonicTimestampGenerator, which never can; any other
generator makes that a per-call decision, so whether timestamps are
assigned client-side is not a property of the configuration at all.
connection.tls.hostname-verification is true for SniSSLOptions, the
driver's only setEndpointIdentificationAlgorithm call, and omitted for
every other SSLOptions, which builds its engine from a user SSLContext
or hands the whole handler to Netty. Both keys are documented as absent
exactly when the behavior is unknown, which is this case. The tls group
can therefore be empty -- its presence is still what reports TLS is on.
Omission is not always available, so these required keys are left in the
one state that is accurate:
- connection.requests.orphaned.max has no 3.x equivalent to report at
all. A request the driver stopped waiting for keeps its stream
identifier until the response arrives, with no configurable bound and
no connection replacement, so the key is omitted -- which its
required-ness then rejects. This is the one violation every report
carries.
- connection.requests.in-flight.max must be positive, while
PoolingOptions also accepts 0. Only PoolingOptions.UNSET falls back to
a protocol default, so a limit of 0 an operator set deliberately is
not reported as 1024.
- query.speculative-execution.policy.percentile is bounded to 0..100
exclusive, while PercentileSpeculativeExecutionPolicy accepts a
percentile of 0.
Such a value is reported as-is and the limitation is documented on the
class: the reporter neither fabricates an in-range value -- which would
misreport a setting an operator may have chosen on purpose, or a policy
3.x does not implement -- nor drops the whole report over one field.
Recorded as a cross-driver schema gap, to be fixed the way
control-plane.schema.agreement.timeout-ms already admits 0.
QueryOptions.setConsistencyLevel now rejects null -- a behavior change
to a public setter. Every query needs a consistency level, so a null
default already failed any statement that did not set one of its own:
SessionManager falls back to it for every request, and
CBUtil.writeConsistencyLevel then dereferences it to write the frame.
That turned a schema-required key into a missing one for a
configuration that could never work. setSerialConsistencyLevel is
deliberately left as it is: the schema makes serial-consistency
optional, so a null there is faithfully reported as an omission rather
than as a missing required key. The reporter keeps omitting a
null it is handed anyway: the field is private, so only a QueryOptions
subclass overriding the getter can still produce one, and letting it
through would throw and cost the whole report rather than one key.
Adds the public getters the report needs: local DC/rack, their explicit
flags and used-hosts-per-remote-DC on DCAwareRoundRobinPolicy, the same
minus used-hosts-per-remote-DC on RackAwareRoundRobinPolicy, replica
ordering on TokenAwarePolicy, and max-executions plus the delay or
percentile on the two built-in speculative execution policies -- whose
parameters are immutable and land in the schema's range exactly, so
they are reported as constant/percentile rather than as custom. Also
makes PagingOptimizingLoadBalancingPolicy implement
ChainableLoadBalancingPolicy so the reporter can unwrap the LB policy
Cluster.Manager wraps at runtime.
in-flight.max needs a fallback because PoolingOptions is still UNSET
when the report is built: the protocol version is only negotiated once
the control connection is up. The default row is resolved with the same
walk PoolingOptions.setProtocolVersion applies -- the highest DEFAULTS
key not above the version -- driven by the version the user pinned with
withProtocolVersion when they pinned one, and by v3 otherwise, that
being the lowest version ScyllaDB negotiates and the reference row for
everything above it. DEFAULTS holds only v1 and v3 rows, so a cluster
pinned to v2 is sized from v1's 128 rather than v3's 1024, and pinning
is the one part of negotiation knowable at report time.
Caps the report at 32KiB of UTF-8 (MAX_DRIVER_CONFIG_LENGTH), matching
the 4.x sibling PR scylladb#968, gocql scylladb#964 and csharp-driver scylladb#262. Beyond
cross-driver parity this is a correctness fix: CBUtil.writeString
writes each STARTUP value with a 16-bit length prefix and no bounds
check, so a value over 65535 bytes truncates the prefix modulo 65536
while still appending the whole body -- a corrupt frame and a failed
handshake, and not something the fail-safe try/catch can contain since
nothing throws. Parts of the report are user-supplied and unbounded
(DC/rack names, consistency levels, custom policy class names). Over
the limit means WARN and no DRIVER_CONFIG.
Hardens the other two ways reporting could break a connection rather
than merely fail to report:
- The fail-safe catch also covers InternalError, since customPolicy()
calls getClass().getSimpleName() on arbitrary user policy objects
(documented JDK edge case for certain synthetic classes). Not a bare
Error, so OutOfMemoryError/StackOverflowError still surface.
- The load balancing policy chain walk is bounded at 16 policies and
shared by both callers. It follows getChildPolicy() on arbitrary user
policies, so a cyclic chain used to spin forever on the Cluster
initialization path -- the one failure mode the try/catch cannot
contain, because it hangs rather than throws.
A custom load balancing policy is now named after the policy the user
configured rather than PagingOptimizingLoadBalancingPolicy. Cluster
.Manager wraps every session's policy in that internal class, and it is
the outermost element of the chain, so every custom policy was reported
as {"type":"custom","name":"PagingOptimizingLoadBalancingPolicy"}. An
anonymous policy class falls back to its binary name, since it has no
simple name and the schema requires a non-empty one.
Adds a JSON-Schema conformance test suite (mirroring the 4.x sibling
PR scylladb#968): the normative schema block is shipped verbatim as a test
resource -- design-doc revision v5, whose report version field is still
1 -- and validated via com.networknt:json-schema-validator (1.5.x,
the last line still targeting Java 8), covering every discriminated-
union branch and optional group the 3.x reporter can emit. Since one
required key has no value to report, the assertion is that a report
violates the schema in exactly the documented ways and no other, with
a test naming the gap and a negative test proving
additionalProperties=false is enforced.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The two places an operator reads when deciding to turn reporting off -- the reference.conf block and the option's own javadoc -- both stopped at "when false, DRIVER_CONFIG is not sent". The asymmetry was written down only in StartupOptionsBuilder.SESSION_ID_KEY, in DriverConfigReporter and in the upgrade guide, none of which is where that decision gets made. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Reading the datacenter and rack the policy resolved meant widening BasicLoadBalancingPolicy#getLocalDatacenter and #getLocalRack from protected to public. The load-balancing manual invites subclassing that class and overriding "only the methods that you wish to modify", and Java does not let an override reduce visibility -- so an existing subclass overriding either one no longer compiles, though it still runs. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both are cases where the documentation admits more than what is reachable or guaranteed. The mixed node-preference variants read as though any combination arises today. None does: BasicLoadBalancingPolicy#init discovers the rack through OptionalLocalRackHelper, which reads configuration and never infers one, and only once a datacenter is known -- so a resolved rack always implies a configured one and inferredRack is null for every built-in. The inferred-rack field serves a subclass overriding discoverLocalRack, which nodeLocation() does read, and now says so. The two new accessors said only that an implementation which knows should override them. That misses the case the reporter is otherwise careful about everywhere it checks an exact class: the built-ins' answers are inherited rather than defaulted to empty, so a subclass that changes the behavior these describe reports its parent's answer unless it overrides them too. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both existing size-limit tests feed a synthetic string through the buildJson() seam, so nothing showed that a report can reach 32KiB at all -- even though unbounded user-supplied values are the whole reason the limit exists. The new test sets a datacenter name half the limit long, which the report carries under both node-preference parents, and pins that the result is a well-formed, schema-valid document dropped for its size rather than a build that failed. Same gap raised against the csharp sibling (scylladb/csharp-driver#263), where the cap was likewise only ever exercised through a test subclass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The message this constructor rejects a negative base-delay with formats a Duration with %d, so String.format raises IllegalFormatConversionException instead -- the operator sees a format error rather than which option is wrong. Pre-existing, and unrelated to configuration reporting beyond this constructor being where the reported delay is now latched; kept as its own commit so it can be dropped without touching the rest. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Fills in the full DRIVER_CONFIG JSON report in the normative cross-driver
schema shape, replacing the stage-1 {"version":1} placeholder. All groups
are populated from Configuration and Policies when the report is built,
i.e. once per Cluster as it initializes.
Everything hangs off three groups. connection carries the connect/read
timeouts, the per-connection request capacity, the pool, the socket
options, the reconnection policy and -- only when TLS is on -- tls.
control-plane carries the system-query and schema-agreement timeouts.
query carries the per-request defaults plus the three policies acting on
a query: retry, load-balancing (with the node preference beside it) and,
when configured, speculative-execution.
The schema reports the node preference in two places and 3.x fills both
from the same policy chain: query.load-balancing.node-preference for what
a query is routed by, connection.node-preference for the part of the
cluster the driver holds connections to. One LoadBalancingPolicy decides
both, since distance(Host) governs whether a host is pooled at all, so
the connection key carries the datacenter half alone. A rack-aware
policy's distance() returns REMOTE, never IGNORED, for a local-datacenter
host in another rack, so those hosts are still pooled and the rack scopes
no pooling at all; the datacenter does, a host outside the preferred one
being IGNORED unless the policy is configured to use hosts there, and an
ignored host gets no pool.
token-aware is the only built-in load balancing shape the schema defines,
so every other built-in policy -- a bare DCAwareRoundRobinPolicy,
RoundRobinPolicy, WhiteListPolicy -- is reported as custom with its class
name, which identifies it but carries none of the normalized flags; its
datacenter and rack still show up in query.load-balancing.node-preference.
A token-aware chain reports load-distribution from its replica ordering
(RANDOM, the 3.x default, is "shuffle"; TOPOLOGICAL is "replica-set";
NEUTRAL keeps the child's plan order, so "round-robin").
fallback-to-non-preferred-nodes is true whenever the policy can reach a
node outside the preference reported beside it. For DCAwareRoundRobin
that means used-hosts-per-remote-DC, since the preference is the
datacenter. RackAwareRoundRobin reports a rack, and the other racks of
its local datacenter are outside that yet are the second tier of every
query plan -- distance() returns REMOTE, not IGNORED, for them -- so it
is always true there, remote datacenter hosts or not.
adaptive-ordering has no "enabled" flag and cannot carry an empty signal
list, so it is reported only when a LatencyAwarePolicy is in the chain --
latency being the only runtime observation a 3.x policy can reorder
candidates on. tls likewise has no "enabled" flag: the group's presence
is what says TLS is on.
Where a configured value falls outside what the schema can express, an
optional key or group is omitted rather than emitted as a value the
schema rejects: a disabled connect timeout, a disabled read timeout (all
three of connection.read, control-plane.queries.system.timeout
.client-side-ms and query.defaults.request), a negative SO_LINGER, a
non-positive socket buffer size, an unbounded page size, and a default
serial consistency level that is not serial -- QueryOptions, unlike
Statement, does not check that one. Two optional bounds are omitted for
the opposite reason -- 3.x has no such bound to report at all:
connection.reconnection.policy.max-attempts, since its reconnection
policies retry forever (the maxAttempts field ExponentialReconnection
Policy carries is an overflow guard on the doubling, not a give-up
bound: past it nextDelayMs() keeps returning maxDelayMs), and
query.retry.policy.max-retries, since no
single number describes a 3.x retry policy. Both built-ins are
parameterless singletons, and while they stop after one attempt on a
read timeout, a write timeout or an unavailable error -- all three
sharing one counter, so one retry between them rather than one each --
onRequestError leaves nbRetry unread and keeps trying the next host
until the query plan runs out. Which of the two applies is decided per
statement rather than by configuration: RequestHandler only consults
onRequestError and onWriteTimeout for an idempotent statement, so the
same policy bounds a non-idempotent request at one retry and an
idempotent one at the length of the query plan, and setIdempotent
overrides the reported query.defaults.idempotence per statement.
Two more keys are omitted for a third reason -- the schema admits only a
boolean, and 3.x cannot observe which one applies.
query.defaults.client-timestamps is false for ServerSideTimestamp
Generator, whose next() always returns Long.MIN_VALUE, and true for an
AbstractMonotonicTimestampGenerator, which never can; any other
generator makes that a per-call decision, so whether timestamps are
assigned client-side is not a property of the configuration at all.
connection.tls.hostname-verification is true for SniSSLOptions, the
driver's only setEndpointIdentificationAlgorithm call, and omitted for
every other SSLOptions, which builds its engine from a user SSLContext
or hands the whole handler to Netty. Both keys are documented as absent
exactly when the behavior is unknown, which is this case. The tls group
can therefore be empty -- its presence is still what reports TLS is on.
Omission is not always available, so these required keys are left in the
one state that is accurate:
- connection.requests.orphaned.max has no 3.x equivalent to report at
all. A request the driver stopped waiting for keeps its stream
identifier until the response arrives, with no configurable bound and
no connection replacement, so the key is omitted -- which its
required-ness then rejects. This is the one violation every report
carries.
- connection.requests.in-flight.max must be positive, while
PoolingOptions also accepts 0. Only PoolingOptions.UNSET falls back to
a protocol default, so a limit of 0 an operator set deliberately is
not reported as 1024.
- query.speculative-execution.policy.percentile is bounded to 0..100
exclusive, while PercentileSpeculativeExecutionPolicy accepts a
percentile of 0.
Such a value is reported as-is and the limitation is documented on the
class: the reporter neither fabricates an in-range value -- which would
misreport a setting an operator may have chosen on purpose, or a policy
3.x does not implement -- nor drops the whole report over one field.
Recorded as a cross-driver schema gap, to be fixed the way
control-plane.schema.agreement.timeout-ms already admits 0.
QueryOptions.setConsistencyLevel now rejects null -- a behavior change
to a public setter. Every query needs a consistency level, so a null
default already failed any statement that did not set one of its own:
SessionManager falls back to it for every request, and
CBUtil.writeConsistencyLevel then dereferences it to write the frame.
That turned a schema-required key into a missing one for a
configuration that could never work. setSerialConsistencyLevel is
deliberately left as it is: the schema makes serial-consistency
optional, so a null there is faithfully reported as an omission rather
than as a missing required key. The reporter keeps omitting a
null it is handed anyway: the field is private, so only a QueryOptions
subclass overriding the getter can still produce one, and letting it
through would throw and cost the whole report rather than one key.
Adds the public getters the report needs: local DC/rack, their explicit
flags and used-hosts-per-remote-DC on DCAwareRoundRobinPolicy, the same
minus used-hosts-per-remote-DC on RackAwareRoundRobinPolicy, replica
ordering on TokenAwarePolicy, and max-executions plus the delay or
percentile on the two built-in speculative execution policies -- whose
parameters are immutable and land in the schema's range exactly, so
they are reported as constant/percentile rather than as custom. Also
makes PagingOptimizingLoadBalancingPolicy implement
ChainableLoadBalancingPolicy so the reporter can unwrap the LB policy
Cluster.Manager wraps at runtime.
in-flight.max needs a fallback because PoolingOptions is still UNSET
when the report is built: the protocol version is only negotiated once
the control connection is up. The default row is resolved with the same
walk PoolingOptions.setProtocolVersion applies -- the highest DEFAULTS
key not above the version -- driven by the version the user pinned with
withProtocolVersion when they pinned one, and by v3 otherwise, that
being the lowest version ScyllaDB negotiates and the reference row for
everything above it. DEFAULTS holds only v1 and v3 rows, so a cluster
pinned to v2 is sized from v1's 128 rather than v3's 1024, and pinning
is the one part of negotiation knowable at report time.
Caps the report at 32KiB of UTF-8 (MAX_DRIVER_CONFIG_LENGTH), matching
the 4.x sibling PR scylladb#968, gocql scylladb#964 and csharp-driver scylladb#262. Beyond
cross-driver parity this is a correctness fix: CBUtil.writeString
writes each STARTUP value with a 16-bit length prefix and no bounds
check, so a value over 65535 bytes truncates the prefix modulo 65536
while still appending the whole body -- a corrupt frame and a failed
handshake, and not something the fail-safe try/catch can contain since
nothing throws. Parts of the report are user-supplied and unbounded
(DC/rack names, consistency levels, custom policy class names). Over
the limit means WARN and no DRIVER_CONFIG.
Hardens the other two ways reporting could break a connection rather
than merely fail to report:
- The fail-safe catch also covers InternalError, since customPolicy()
calls getClass().getSimpleName() on arbitrary user policy objects
(documented JDK edge case for certain synthetic classes). Not a bare
Error, so OutOfMemoryError/StackOverflowError still surface.
- The load balancing policy chain walk is bounded at 16 policies and
shared by both callers. It follows getChildPolicy() on arbitrary user
policies, so a cyclic chain used to spin forever on the Cluster
initialization path -- the one failure mode the try/catch cannot
contain, because it hangs rather than throws.
A custom load balancing policy is now named after the policy the user
configured rather than PagingOptimizingLoadBalancingPolicy. Cluster
.Manager wraps every session's policy in that internal class, and it is
the outermost element of the chain, so every custom policy was reported
as {"type":"custom","name":"PagingOptimizingLoadBalancingPolicy"}. An
anonymous policy class falls back to its binary name, since it has no
simple name and the schema requires a non-empty one.
Adds a JSON-Schema conformance test suite (mirroring the 4.x sibling
PR scylladb#968): the normative schema block is shipped verbatim as a test
resource -- design-doc revision v5, whose report version field is still
1 -- and validated via com.networknt:json-schema-validator (1.5.x,
the last line still targeting Java 8), covering every discriminated-
union branch and optional group the 3.x reporter can emit. Since one
required key has no value to report, the assertion is that a report
violates the schema in exactly the documented ways and no other, with
a test naming the gap and a negative test proving
additionalProperties=false is enforced.
The report is now built behind a guard at its one call site, so a
classpath without Jackson cannot break connecting. Stage 1 made
jackson-core/jackson-databind required compile-scope dependencies of
driver-core, and DefaultDriverConfigReporter holds an ObjectMapper in a
static field: exclude jackson-databind and merely initializing that class
raises NoClassDefFoundError. That is an Error, raised while initializing
the class rather than from any method it declares, so neither the
reporter's own fail-safe nor its caller could contain it, and it happens
on the Cluster initialization path -- so a classpath that merely lacks an
optional serializer went from "the report is skipped" to "no connection
can be established", the inverse of the invariant this class is written
around. Connection.Factory.buildDriverConfigReport now catches
LinkageError -- not just NoClassDefFoundError, so a version-mismatched
Jackson surfacing as ExceptionInInitializerError is covered too, where
probing one class name would pass and still fail -- and reports nothing,
logging at WARN since reporting ships enabled and nobody opted in. Same
fallback SnappyCompressor already applies for its own optional library,
and no contradiction with buildReport() deliberately not catching bare
Error: that is about report building never masking a real JVM failure,
this is a call site tolerating a missing optional dependency.
Both node preference slots are documented as an approximation once a
wrapper sits above the policy they were read from. HostFilterPolicy
.distance() -- and so WhiteListPolicy's, which extends it -- returns
IGNORED for any host failing its predicate, including one inside the
reported datacenter, and a custom chainable policy computes distance()
itself and need honor nothing below it. The configured datacenter is
reported anyway, on the grounds that hiding one the operator really did
set is worse, and the asymmetry is deliberate: nothing is inferred on a
third party's behalf, but what was configured is passed through. The
restriction has nowhere to go, the built-in shape having no room for a
wrapper and fromDCWhiteList collapsing its datacenters into an opaque
Predicate<Host>.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
What ☑️
Stage 2 (the payload) of driver configuration reporting: replaces the stage-1
{"version":1}placeholder with the full
DRIVER_CONFIGreport — the effective configuration of the driver'sdefault execution profile plus the context's policies, serialized to the normative cross-driver JSON
schema shape. Stage 1 (#967) is merged; this branch is rebased onto
scylla-4.x, so there is nostage-1 noise in the diff.
Gated behind
advanced.driver-config-reporting.enabled, which ships enabled (per@dkropachev's cross-driver review). Turning it off suppresses only the
DRIVER_CONFIGblob —SESSION_IDrides on every connection unconditionally, independently of this flag, so "off" is not"zero change on the wire".
Read the commits in order; each is formatter-clean and green on its own.
The report 🧩
Built from the default execution profile + the context's policies, and rebuilt on every
control-connection init, so it always reflects the current (possibly runtime-reloaded) config.
Three groups:
connection(connect timeout, request capacity, pooling, socket options,reconnection policy, TLS when on, and the datacenter preference that scopes pooling),
control-plane(internal-query and schema-agreement timeouts), andquery(per-requestdefaults, plus the retry, load-balancing and speculative-execution policies).
Against the shipped default configuration, 938 bytes (pretty):
{ "version": 1, "connection": { "connect": { "timeout-ms": 5000 }, "requests": { "in-flight": { "max": 1024 }, "orphaned": { "max": 256 } }, "pool": { "shard-aware": { "enabled": true } }, "socket": { "tcp-no-delay": true, "keep-alive": false, "reuse-address": false }, "reconnection": { "policy": { "type": "exponential", "base-ms": 1000, "max-ms": 60000 } }, "node-preference": { "type": "dc-auto" } }, "control-plane": { "queries": { "system": { "timeout": { "client-side-ms": 5000 } } }, "schema": { "agreement": { "timeout-ms": 10000 } } }, "query": { "defaults": { "page": { "size": 5000 }, "consistency": "LOCAL_ONE", "serial-consistency": "SERIAL", "idempotence": false, "client-timestamps": true, "request": { "timeout-ms": 2000 } }, "retry": { "policy": { "type": "standard-error-aware" } }, "load-balancing": { "policy": { "type": "token-aware", "load-distribution": "shuffle", "fallback-to-non-preferred-nodes": false, "adaptive-ordering": { "signals": [ "response-rate", "in-flight-requests", "recovery-state" ] } }, "node-preference": { "type": "dc-auto" } } } }Verified on the wire by a
tsharkcapture of theSTARTUPframes against a single-node CCMScyllaDB (protocol v4, default config):
SESSION_IDon every connection,DRIVER_CONFIGonly on thecontrol connection, and gone when the flag is off. Verified end to end through
system.clients.client_options(ScyllaDB 2026.1) andsystem_views.clients(Cassandra 4.1),untruncated, with the one backend-conditional key differing as it should.
Invariants 🔒
SESSION_IDis still emitted and only the config blob is dropped.
RuntimeExceptiononly — deliberately notbare
Error, soOutOfMemoryError/StackOverflowErrorstill surface. The oneErrorthis classcan provoke is the
InternalErrorthatgetClass().getSimpleName()raises for certain syntheticclasses; that is caught at the call site, behind a package-private seam so the branch stays
testable.
manual/core/integrationdocuments that the driver "can operatenormally without" Jackson, and
DefaultDriverContextalready honours that for Insights by checkingDefaultDependencyChecker.isPresent(JACKSON)before building the listener. Reporting was a thirdJackson user with no such check, and it is default-on and on the connection-init path — so on that
classpath linking
DefaultDriverConfigReporterraisedNoClassDefFoundError, anErrorraisedwhile resolving the class rather than from any method it declares. Neither the
try/catchabovenor
ProtocolInitHandlercould contain it: every control connection failed and the session couldnot be built. Now the implementation is chosen up front, falling back to a
NoopDriverConfigReporterthat names no Jackson type anywhere (one reference would make loading it fail for exactly the
deployments it exists to serve). Logged unconditionally, unlike Insights: nobody opted in to
reporting, so nobody would think to look for a message saying it is off. Verified both ways against
core's real runtime classpath with its Jackson jars removed.
DefaultSession.init()already forces eagerly. It was the one component on the reporting path left lazy, which made a
Netty event loop the first thread to load it and Jackson — jar reads, mid-
STARTUP. This is alsowhat makes the ordering
buildJson()'s javadoc relies on true by construction.STARTUPoptionvalues go through
ByteBufPrimitiveCodec.writeString, which writes a 16-bit length prefix viaByteBuf.writeShortwith no bounds check, so a value over 65535 bytes silently truncates theprefix modulo 65536 while still appending the whole body — a corrupt frame and a failed handshake,
and not something the
try/catchcan save, since nothing throws. Parts of the report areuser-supplied and unbounded (DC/rack names, consistency levels, custom policy class names), so
without this the "reporting must never prevent a connection" invariant simply wasn't true. Measured
on the UTF-8 bytes, since that is what the prefix counts.
as
null. Same where an optional key's configured value is outside what the schema can express(a disabled request timeout, a disabled
SO_LINGER, an unbounded page size), and same where theanswer is genuinely unknown — the two cases the schema made optional for exactly that purpose.
no-fallback getters, which throw on an absent option, so a config source omitting any one of them
dropped all ~34 fields behind a single WARN. Every read now either sits behind
isDefinedorpasses an explicit fallback, and the schema picks which: an optional field falls back to the same
"disabled" sentinel that already omits it, a required one to the value
reference.confdocuments.Design decisions worth questioning 📐
instanceof— so a user subclass of abuilt-in falls through to
{type:"custom", name:<class>}instead of being misreported as theunmodified built-in.
connection.reconnection.policyand
query.speculative-execution.policydescribe the policy that is actually reconnecting andspeculating. The built-ins latch these numbers into final fields when the context builds them, and
advanced.speculative-execution-policyis documented as not modifiable at runtime, so areloaded profile can carry values no request executes with — and, unlike a constructor, admits
values the schema rejects: a negative
delay-ms, or amax-executionsof 1 that would drop thewhole group while the policy still speculates. Reading the instance makes those ranges hold by
construction.
adaptive-orderingandfallback-to-non-preferred-nodeswere the last two readingthe profile — the class javadoc used to name them as the exception — and now read
DefaultLoadBalancingPolicy.isAvoidingSlowReplicas()andBasicLoadBalancingPolicy.getMaxNodesPerRemoteDc()instead, so no latched value is described froma profile the running policy has not adopted. Raised by @dkropachev for the first; the second is
the same defect one field over, fixed alongside it.
control-plane.queries.system.timeout.server-side-msreports configuration, not effect.CassandraSchemaQueriesadds aUSING TIMEOUTclause built fromadvanced.metadata.schema.request-timeoutonly where
shouldApplyUsingTimeout()sees sharding info, so on generic Cassandra the option is aclient-side wait alone. This was gated on that signal until @dkropachev asked for the configured
value regardless of peer detection — the way
pool.shard-aware.enabledalready reports intent. Thecost is that an operator on Cassandra 4.1 reads a server-side timeout nothing enforces, which wants
the schema description to say so; the gain is that the report no longer depends on anything the
peer said, which removed the
NodeShardingInfoargument entirely.getSslHandlerFactory(), notgetSslEngineFactory()(per@sylwiaszunejko). The handler factory is the reference
ChannelFactoryinstalls the SSL handlerfrom, and
buildSslHandlerFactory()is the documented expert extension point (e.g. Netty's nativeOpenSSL): an override supplies no engine factory, so reading the engine factory reported such a
session as plaintext when it is in fact encrypted. Host name validation is then read off the engine
factory the active handler actually wraps — never through the context, which can name a different,
unused one and whose
LazyReferencethe reporter would be the first to force (keystore reads on aNetty event loop, mid-
STARTUP).SslEngineFactory.isHostnameValidationRequired()andTimestampGenerator.isClientSide()returnOptional<Boolean>, empty by default. Host namevalidation is a property of the JDK
SSLEngine, unreadable through an opaque handler factory; anda custom
TimestampGeneratoris free to returnStatement.NO_DEFAULT_TIMESTAMPand delegate tothe coordinator, which no class check can detect and which calling
next()to find out would haveside effects. Both keys are now optional in the schema with absence defined as unknown, so an
implementation that cannot answer is reported by omission rather than by a guessed boolean — which
for these two fields would misdescribe a security control and a write-timestamp source. Both
methods are
default, so existing implementations keep compiling. Not a Java-local flourish:@dkropachev asked Bump ch.qos.logback:logback-classic from 1.2.3 to 1.2.13 #263 for exactly this shape on both fields — emit the boolean only where it
is known, omit it for custom or unknown behaviour — and cited this PR's timestamp accessor by
name as the model. (He also asked Bump ch.qos.logback:logback-classic from 1.2.3 to 1.2.13 #263 to derive
client-timestampsfrom the negotiatedprotocol, since
SupportsTimestamp()starts at v3; Java 4.x supports nothing below v3, so thereis nothing to gate on here.) One thing the javadocs now spell out: a subclass of a built-in
inherits its parent's answer rather than the empty default, so a subclass that changes what
these describe has to override them too.
holds these options as
Durationand schedules several in nanoseconds, so truncating a 500 µstimeout to
0would report a live timeout as the very value the field defines as off. Applies toschema.agreement.timeout-ms,queries.system.timeout.client-side-ms,query.defaults.request.timeout-msandreconnection.policy.delay-ms. Three fields aredeliberately exempt, because
0is what they really mean there:connection.connect.timeout-ms(Netty's
CONNECT_TIMEOUT_MILLIStruncates identically, and 0 disables it),...server-side-ms(the value goes on the wire as a
USING TIMEOUTmillisecond argument, so sub-millisecond reallyis
0msserver-side) andspeculative-execution.policy.delay-ms(reference.confdocumentssub-millisecond delays as equivalent to 0).
connection.requests.orphaned.maxis the effective threshold, not the configured one.ChannelFactoryrequiresmax-orphan-requeststo stay belowmax-requests-per-connectionandsilently substitutes a quarter of the latter otherwise. Reporting the configured value would
describe a threshold no connection was built with, so the correction lives in one place —
ChannelFactory.effectiveMaxOrphanRequests(), which the channel setup itself calls.node-preferenceslots are filled differently, because in Java they mean differentthings.
computeNodeDistancederives node distance from the local DC alone — a node outside it isIGNORED, and anIGNOREDnode gets no pool — so the datacenter genuinely scopes which nodes areconnected to, and goes under
connection.node-preference. The rack never reaches that method: itonly reorders replicas at the head of a query plan, with connections still held across the whole
local DC. So the full preference (rack included) goes under
query.load-balancing.node-preference,and the connection group carries the datacenter half alone. Emitting the same object in both would
claim a rack-scoped connection pool that does not exist.
load-distributionisshuffleandadaptive-orderingmaps to slow-replica avoidance. Thebuilt-ins shuffle the replica head of every query plan unconditionally
(
BasicLoadBalancingPolicy.shuffleHead, no config to disable), soround-robinwould describe onlythe non-replica tail and
replica-setwould claim the order is untouched (see A1 for the one casethis misses). Java has no latency-percentile ordering, so
adaptive-orderingmaps to the one realmechanism,
DefaultLoadBalancingPolicy's slow-replica avoidance, with its signals read offavoidSlowReplicasrather than guessed — andlatencydeliberately absent, since those samplesrecord when responses arrived, not how long they took. Its presence is also now the only thing
distinguishing
BasicLoadBalancingPolicyin the report, which has no such mechanism at all.Spec conformance 🔍
The v1 schema is shipped verbatim as a test resource, byte-identical to the design document's
schema block, and every representative report is validated against it in
DefaultDriverConfigReporterTest— enforced, not asserted. A negative test confirms the validatoractually rejects an out-of-schema document.
Every report a stock configuration can produce validates. Two required fields are constrained more
tightly than the option behind them, but only one is reachable through a running driver. Both are
reported truthfully and pinned by tests that assert the violation:
query.defaults.consistencyis a closed enum whilebasic.request.consistencyis an unvalidatedstring. The built-in load balancing policies resolve it through the
ConsistencyLevelRegistryintheir constructor, so an unknown name fails the session before any report exists — reaching this
needs a custom registry defining extra names, which is the case CodeRabbit raised. This is the
one real gap.
connection.requests.in-flight.maxmust be positive, and nothing validatesadvanced.connection.max-requests-per-connectionagainst that —ChannelFactoryhands the valuestraight to
StreamIdGenerator, which does not range-check it. An earlier revision of thisdescription claimed such a setting starts a session; it does not. The connection fails first: a
negative value makes
StreamIdGenerator'sBitSetthrow whileChannelFactoryis still buildingthe channel, and
0leaves no stream id for the control connection's ownOPTIONS, whichChannelHandlerRequestfails onpreAcquireIdbeforeSTARTUPis composed. So this is unreachableby construction, not a live exposure. The value is still passed through and still pinned, so the
behaviour stays defined if the driver ever stops failing that early. (The same setting would also
drive
orphaned.maxnegative — a second reason to read it as one unreachable shape rather than onefield's gap.) Worth one cross-driver note, since Bump ch.qos.logback:logback-classic from 1.2.3 to 1.2.13 #263 was asked to change this very field:
there the reported number was the pool-admission threshold rather than the stream-id pool, and
@dkropachev asked for
Connection.GetMaxConcurrentRequests(128 or 2048) instead. In Java thetwo are one number —
ChannelFactoryisnew StreamIdGenerator(maxRequestsPerConnection)—so the configured value already is the stream-id pool size and needs no such correction.
A third shape was reachable until the push before last:
query.speculative-execution.policytook both its numbers from the profile, so a reload could put a negative
delay-ms— whichnonNegativeIntegerrejects — into an otherwise valid document, or drop the group while the policystill speculated. Both now come off the policy, whose constructor admits neither.
Fabricating an admissible value would misreport a setting an operator may have chosen deliberately,
and dropping the whole report would punish every other group for one field.
Approximations, flagged not changed⚠️
load-balancing.policy.load-distributionshufflenewQueryPlanPreserveReplicas, which never shuffles —replica-setin schema terms — anddefault-lwt-request-routing-methodships asPRESERVE_REPLICA_ORDER. So every LWT statement on a default config is distributed the way the report says it is not. No single enum value is honest.load-balancing.policy.fallback-to-non-preferred-nodesmax-nodes-per-remote-dc > 0and a datacenter preference existsRoundRobinPolicy, "for rr, there is no remote nodes or nodes outside of the node preferences, so having ittruewill be confusing, and yes, having it asfalsewill be less confusing, not having it at all would be better, but there is no good way to do that" (the tail of that is now a schema follow-up). One term is still missing.maybeAddDcFailoveralso consultsisDcFailoverAllowedForRequest, false for a DC-local consistency whileallow-for-local-consistency-levelsis off — and both of those ship as the default, so on a config that changes nothing butmax-nodes-per-remote-dcthe report saystruewhile no ordinary statement fails over. Note the "it's per-request, a statement can override it" argument does not carry on its own:query.defaults.consistencyis published under the same caveat. The real cost is that closing it needsConsistencyLevelRegistryresolution of a string this report deliberately passes through unvalidated. A schema value meaning "conditional" is the honest fix.connection.socket.keep-alive,.reuse-addressfalsewhen unsetStandardSocketOptionsdocuments as system dependent.falseholds for JDK NIO on Linux; unverified for the native transports. Both keys are required, so omission is not available. Narrower than it looks beside #263, where @dkropachev found csharp'sReuseAddresswas never wired toSO_REUSEADDRat all:DefaultNettyOptionsdoes set bothChannelOptions whenever the option is defined, so only the unset case is approximated here.control-plane.queries.system.timeout.client-side-msCONTROL_CONNECTION_TIMEOUTMETADATA_SCHEMA_REQUEST_TIMEOUT, so the two siblings do not describe the same query — an operator debugging a slow schema query reads the wrong number. Already on the thread with @dkropachev; the fix is aqueries.schemasibling, blocked today byadditionalProperties:false.node-preferencedatacenter / rack values""is reported as no preference whileOptionalLocalDcHelper/OptionalLocalRackHelperhand it to the policy as a set-but-unmatchable datacenter. No alternative:nonEmptyStringleaves no way to report"", andtype:"dc"with the key omitted is invalid too. A padded value is no longer normalized —nonEmptyStringisminLength: 1, so" dc1 "is valid to emit and trimming it hid the typo an operator opens this report to find (raised by @dkropachev). Normalizing the runtime helpers instead was declined: that changes routing, in a reporting PR.query.load-balancing.node-preferencetype:"rack"DefaultLoadBalancingPolicy;BasicLoadBalancingPolicynever readslocalRack, andPRESERVE_REPLICA_ORDERignores it as well. Kept — the value is configured, and hiding a real setting is the worse failure mode.node-preferenceslots, when a node-distance evaluator is configuredbasic.load-balancing-policy.evaluator.classis consulted bycomputeNodeDistancebefore the datacenter and its verdict returned directly, so it can leave an in-DC nodeIGNOREDand without a pool. Nothing can be reported for it: the option names a user-supplied class, the driver ships no location-based evaluator to introspect, andnode-location-preferencehas no slot for a class name. Raised by @dkropachev on the gocql sibling, whereDataCenterHostFilteris introspectable.node-preferenceslots, for a custom load balancing policyconnection's says the DC decides which nodes hold a pool, which holds becauseBasicLoadBalancingPolicy#computeNodeDistancemakes an out-of-DC nodeIGNORED;query.load-balancing's says it scopes routing. A custom policy computes distance itself and need not readlocal-datacenterorwithLocalDatacenterat all. Kept on A6's grounds. Deliberately asymmetric with the no-DC case, where the group is omitted rather than reporting adc-autothe SPI never promises: nothing is inferred on a custom policy's behalf, while what was configured is passed through.Two cosmetic ones, noted for completeness: a negative
schema.agreement.timeout-msnormalizes to0(same outcome as
0, one extra round trip, and the schema cannot say "negative"); andconnection.connect.timeout-msis reported as a fulllongwhileDefaultNettyOptionsnarrows itwith
intValue(), so a connect timeout past ~24.8 days wraps in Netty.Follow-up ⏭️
For the schema owner — all for the document rather than here.
core/src/test/resources/config/driver-config-report-v1.schema.jsonis a byte-for-byte copy of the document's normative block, so every item below lands there first
and the vendored copy is resynced afterwards. Adding a key here to close a review comment would fork
the contract and leave this driver's conformance suite validating against a schema no other
implementation has.
describe fields the schema no longer has, and both sample payloads still show the pre-restructure
flat envelope, so they fail validation against the document's own schema.
$idandversionstill say v1 /const: 1although earlier revisions removed a requiredtop-level group and renamed load-balancing fields. By the schema's own versioning rule that is a
major bump; harmless while every implementation is unreleased, but a v1 consumer cannot tell the
shapes apart.
dc-autocarries the inferred value in plainlocal-dcwhilerack-autouses an explicitinferred-prefix. Implemented as specified; the asymmetry is easy to misread.node-location-preferencehas no "no preference" variant (raised by @dkropachev). Omitting theoptional group is the schema-valid answer and is what this PR does, but a
nonetype would say itpositively.
query.defaults.consistencyneeds either a wider type or a documented rule for names outside itsenum — the one conformance gap a running Java driver can still produce. (An earlier revision of
this list also asked the spec to define consumer behaviour for a non-positive
in-flight.max;withdrawn — see Spec conformance, no session can reach it.)
control-plane.queries.system.timeoutgroupsclient-side-msandserver-side-msas two views ofone timeout. For Java they are not — see A4; a
queries.schemasibling would let each class ofquery carry an honest pair. Agreed on the thread, and note the two asks interact: once
queries.schemaexists,METADATA_SCHEMA_REQUEST_TIMEOUTbelongs there rather than underqueries.system, so theserver-side-msthis branch ungated will migrate.server-side-msneeds the "reports configuration intent" clausepool.shard-aware.enabledalreadycarries, now that it is emitted on backends where no
USING TIMEOUTclause is ever sent.connection.poolshould carrylocal.sizeandremote.size(requested by @dkropachev; bothoptions are always configured and consumed by
ChannelPool). Blocked here:$defs/connection-poolis
additionalProperties: falseand this branch ships the schema block verbatim. Two things tosettle in the shape — whether a size of
0is representable, sincepositiveIntegerwouldreproduce the objection raised against the old
desired-connections-count; and thatChannelPool.initialize()ceil-divides the configured size across shards, solocal.size = 1on a4-shard node opens four connections and the number Java reports is not the connection count.
speculative-execution.policy.percentileisexclusiveMinimum: 0, while 3.x'sPercentileSpeculativeExecutionPolicyaccepts0.0— so an accurate report of that configurationis out of schema (raised by @dkropachev on Client config reporting (3.x) — stage 2: full DRIVER_CONFIG report #974). Unreachable from Java 4.x, which has no percentile
policy at all, but the schema is shared.
fallback-to-non-preferred-nodesshould be optional, so that "there is no node preference,therefore no non-preferred nodes to leave" can be said by omission rather than by a
falsethatreads like a disabled feature. This is the tail of @dkropachev's Bump ch.qos.logback:logback-classic from 1.2.3 to 1.2.13 #263 comment quoted in A2 —
"not having it at all would be better, but there is no good way to do that" — and it retires
half of A2.
standard-error-awarehas no normative rule set: its whole description is "Standarderror-aware retry policy." @dkropachev challenged the csharp mapping on rules the spec does not
state (csharp's
DefaultRetryPolicynever retriesUnavailable). Java's does, so thatobjection does not transfer — but no implementation's mapping is checkable until the type says
what it means.
Other:
Client config reporting (3.x) — stage 2: full DRIVER_CONFIG report #974's and Bump ch.qos.logback:logback-classic from 1.2.3 to 1.2.13 #263's (39128 bytes each); stage 2: populate the DRIVER_CONFIG report gocql#987's differs in exactly two places —
$defs/requests/requireddropsorphaned, andorphaned.max's description gains "Absent onlywhen this bound is unknown, for example when the client never replaces a connection over
accumulated orphans and so has no limit to report." So either the document moved past the
revision these three track, or that copy was edited locally and gocql's conformance suite
validates against a forked contract. Asked on that thread, unanswered. Deliberately not
resolved here: the resource has to stay a byte-for-byte copy of the document's normative
block, and either way the change is permissive — Java always has an orphan limit, so nothing
this branch emits changes.
restructure. The sub-millisecond reconnection floor does not carry over: 3.x's
ConstantReconnectionPolicyholds along delayMs, so there is no sub-millisecond value totruncate. Separate PR, separate branch. Traffic goes the other way too: the
speculative-execution source-of-truth fix on this branch was raised there first, and 3.x additionally
had to stop reporting both built-ins as
custom, which this branch never did.default-on flip is intentional; reasoning is on the threads.
DriverBlockHoundIntegrationITis JDK 14+ only and was not run locally. With reporting on bydefault the report is built on a Netty event loop; reasoned safe (no SSL factory resolution or IO
with the default config, and Jackson is in-memory), but worth watching in CI. The larger half of
that risk is gone: the reporter is now resolved during session init, so the event loop is no longer
the first thread to load it and Jackson.
here.
manual/render with a spurious#prefix (href="#../configuration/reference/") — a site-wideMyST artifact affecting pre-existing links too, so it wants its own issue rather than a partial fix
here. The one link this PR would have added was dropped for that reason.
🤖 Generated with Claude Code